#ifndef KPEXTCOMMANDBUFFER_H
#define KPEXTCOMMANDBUFFER_H

#include "kpextCommonDef.h"

namespace kpext {

//-----------------声明----------------------
class CommandBuffer;
class IRecordCommands;
//------------------------------------------

/**
 * @brief vulkan commandpool和commandbuffer的简单封装
 */
class CommandBuffer
{
public:
    /**
     * @brief 构建vkcommandbuffer扩展功能对象
     * @param ctx  kpextVkContext vulkan上下文环境变量
     * @param queueIndex  希望使用kpextVkContext的队列家族索引
     * @param poolflag 与命令缓冲一起创建的命令池属性标记, 可通过vk::CommandPoolCreateFlags()传入默认值
     * @param isPrimary 指示是否是主要缓冲区
     * @param totalTimestamps  参考kp::Sequence
     * @param cmdpool 指定从哪个commandpool分配命令缓冲， 如果为null则在内部自动创建commandpool
     */
    explicit CommandBuffer(std::shared_ptr<vk::Device> device,
                           std::shared_ptr<vk::Queue> computeQueue,
                           uint32_t queueIndex,
                           vk::CommandPoolCreateFlags poolflag = vk::CommandPoolCreateFlagBits::eResetCommandBuffer,
                           bool isPrimary = true,
                           uint32_t totalTimestamps = 0,
                           std::shared_ptr<vk::CommandPool> cmdpool = nullptr
                           );

    virtual ~CommandBuffer();


    /**
     * @brief 设置内部使用的 vulkan queue 访问互斥锁
     * @param mutex  互斥锁
     */
    virtual void setQueueAccessMutex(
            std::shared_ptr<std::mutex> mutex);

    /**
     * @brief 查询命令池是否正常构建
     * @return  命令池正常构建返回true, 否则返回false
     */
    virtual bool isCommandBufferOk();

    /**
     * @brief 获取命令缓冲区是否是主要命令缓冲区
     * @return  如果是主要命令缓冲区返回true
     */
    virtual bool isPrimaryLevel();

    /**
     * @brief 获取命令缓冲区是否是次要命令缓冲区
     * @return  如果是次要命令缓冲区返回true
     */
    virtual bool isSecondaryLevel();

    /**
     * @brief 判断与目标命令缓冲是否使用相同的命令池申请的
     * @param cmdbuf  目标命令缓冲
     * @return 相同返回true，否则返回false
     */
    virtual bool isSameCmdPool(const kpext::CommandBuffer & cmdbuf);

    /**
     * @brief 判断与目标命令池是否使用相同的命令队列申请的
     * @param cmdbuf  目标命令缓冲
     * @return 相同返回true，否则返回false
     */
    virtual bool isSameQueue(const kpext::CommandBuffer & cmdbuf);

    /**
     * @brief 以当前对象为模板，在同一个命令池分配一个新的命令缓冲区， 可指定是主要还是次要级别
     * @param isPrimary 主要还是次要级别命令缓冲区
     * @param poolflag 与命令缓冲一起创建的命令池属性标记, 可通过vk::CommandPoolCreateFlags()传入默认值
     * @param totalTimestamps  参考kp::Sequence
     * @return  成功返回kpext::CommandBuffer共享指针， 失败返回空指针
     */
    virtual std::shared_ptr<kpext::CommandBuffer>
    allocateCmdBuf(bool isPrimary = true,
                   vk::CommandPoolCreateFlags poolflag = vk::CommandPoolCreateFlagBits::eResetCommandBuffer,
                   uint32_t totalTimestamps = 0
            );

    /**
     * @brief 获取vulkan命令缓冲对象引用， 请勿在外部调用销毁(destroy())
     * @return 命令缓冲对象引用
     */
    virtual vk::CommandBuffer & getVKCmdBuf();

    /**
     * @brief 获取内部的vulkan命令池对象共享指针， 请勿在外部销毁它(destroy())
     * @return 命令池对象共享指针
     */
    virtual std::shared_ptr<vk::CommandPool> getVKCmdPool();

    /**
     * @brief 开始记录命令
     */
    virtual void begin();

    /**
     * @brief 结束记录命令
     */
    virtual void end();

    /**
     * @brief 发命令缓冲中的命令到命令池中执行
     */
    virtual void submit();

    /**
     * @brief 查询获取submit()后，命令是否执行完毕
     * @return submit()后，命令执行完毕返回true, 否则返回false
     */
    virtual bool isExecCompleted();

    /**
     * @brief submit()后， 阻塞等待执行完毕，等待时长单位为纳秒
     * @param waitFor 最长的等待时长.单位为纳秒
     */
    virtual void waitExec(uint64_t waitFor = UINT64_MAX);

    /**
     * @brief 将命令缓冲对象复位到初始状态
     */
    virtual void reset();

    /**
     * @brief 记录次要命令缓冲区的命令到命令缓冲区中, 本对象不是主要命令缓冲区请勿调用
     * @param secondaryCmdBuf  次要级别命令缓冲区
     */
    virtual void record(std::shared_ptr<kpext::CommandBuffer> secondaryCmdBuf);

    /**
     * @brief 从命令记录接口记录命令
     * @param 命令记录接口实例对象共享指针
     */
    virtual void record(kpext::IRecordCommands * irecord);

#ifdef KPEXT_USE_KOMPUTE
    /**
     * @brief 记录kp::OpBase的命令， 调用方应保证本类对象没有在执行中
     * @param op  kp::OpBase
     */
    virtual void record(std::shared_ptr<kp::OpBase> op);

    /**
     * @brief 记录kp::OpBase的命令重载函数， 调用方应保证本类对象没有在执行中
     * @param op  kp::OpBase
     */
    virtual void record(kp::OpBase * op);

    /**
     * @brief 功能与kp::Sequence::record<>类似
     * @param tensors
     * @param params
     * @return
     */
    template<typename T, typename... TArgs>
    void record(
            std::vector<std::shared_ptr<kp::Tensor>> tensors,
            TArgs&&... params)
    {
        std::shared_ptr<T> op{ new T(tensors, std::forward<TArgs>(params)...) };
        this->record(op);
    }

    /**
     * @brief 功能与kp::Sequence::record<>类似
     * @param params
     * @return
     */
    template<typename T, typename... TArgs>
    void record(std::shared_ptr<kp::Algorithm> algorithm,
                TArgs&&... params)
    {
        std::shared_ptr<T> op{ new T(algorithm,
                                     std::forward<TArgs>(params)...) };
        this->record(op);
    }
#endif

protected:

    /**
     * @brief 创建命令池要使用的逻辑设备对象
     */
    std::shared_ptr<vk::Device> m_device;

    /**
     * @brief 提交命令要使用到的队列, 命令池所属的命令队列
     */
    std::shared_ptr<vk::Queue> m_queue;

    /**
     * @brief 防止m_queue vulkan 队列多线程同时访问的互斥锁
     */
    std::shared_ptr<std::mutex> m_mutexQueue;

    /**
     * @brief m_queue对应的命令家族索引，用于创建命令池
     */
    uint32_t m_queueIndex;

    /**
     * @brief 是否主要缓冲区标记
     */
    vk::CommandBufferLevel m_bufLevel;

    /**
     * @brief 是否要释放命令池标记
     */
    bool m_FreeCommandPool;

    /**
     * @brief 创建的命令池对象
     */
    std::shared_ptr<vk::CommandPool> m_CommandPool;

    /**
     * @brief 是否要释放命令缓冲标记
     */
    bool m_FreeCommandBuffer;

    /**
     * @brief 创建的命令池缓冲对象
     */
    std::shared_ptr<vk::CommandBuffer> m_CommandBuffer;

    /**
     * @brief 用于同步操作的栅栏对象
     */
    vk::Fence m_Fence;

    /**
     * @brief 是否在提交执行命令中标记
     */
    bool m_IsRunning;

    /**
     * @brief 是否正在记录命令中标记
     */
    bool m_IsRecording;

    /**
     * @brief 创建命令池对象
     * @param flag 创建命令池对象的属性类型
     */
    virtual void createCommandPool(vk::CommandPoolCreateFlags flag);

    /**
     * @brief 创建命令池缓冲对象
     */
    virtual void createCommandBuffer();

};  // class kpext::CommandBuffer

/**
 * @brief 命令记录接口类
 */
class IRecordCommands
{
public:
    virtual ~IRecordCommands(){}

    /**
     * @brief 记录命令函数
     * @param cmdbuf
     */
    virtual void record(const vk::CommandBuffer& commandBuffer) = 0;
}; //class kpext::IRecordCommands;



} // namespace kpext


#endif // KPEXTCOMMANDBUFFER_H
